1.4. Functions
函数将代码分解为可管理的部分并减少代码重复。函数可能采用零个或多个参数作为输入,并且它们返回特定类型的单个值。函数声明(declaration)或原型(prototype)指定函数的名称、返回类型及其参数列表(所有参数的数量和类型)。函数定义(definition)包括调用函数时要执行的代码。 C 中的所有函数都必须在调用之前声明。这可以通过声明函数原型或在调用函数之前完全定义该函数来完成:
// function definition format:
// ---------------------------
<return type> <function name> (<parameter list>)
{
<function body>
}
// parameter list format:
// ---------------------
<type> <param1 name>, <type> <param2 name>, ..., <type> <last param name>
这是一个函数定义示例。请注意,注释描述了函数的作用、每个参数的详细信息(其用途和应传递的内容)以及函数返回的内容:
/* This program computes the larger of two
* values entered by the user.
*/
#include <stdio.h>
/* max: computes the larger of two integer values
* x: one integer value
* y: the other integer value
* returns: the larger of x and y
*/
int max(int x, int y) {
int bigger;
bigger = x;
if (y > x) {
bigger = y;
}
printf(" in max, before return x: %d y: %d\n", x, y);
return bigger;
}
没有返回值的函数应指定 void
返回类型。以下是 void
函数的示例:
/* prints out the squares from start to stop
* start: the beginning of the range
* stop: the end of the range
*/
void print_table(int start, int stop) {
int i;
for (i = start; i <= stop; i++) {
printf("%d\t", i*i);
}
printf("\n");
}
与任何支持函数或过程的编程语言一样,函数调用会调用函数,为特定调用传递特定的参数值。函数通过其名称来调用,并传递参数,每个相应的函数形参(parameter)都有一个实参(argument)。在 C 语言中,调用函数如下所示:
// 函数调用格式:
// ---------------------
function_name(<argument list>);
// 参数列表格式:
// ---------------------
<argument 1 expression>, <argument 2 expression>, ..., <last argument expression>
C 函数的参数按值传递(passed by value):每个函数形参(parameter)都分配有调用者在函数调用中传递给它的相应实参(argument)的值。按值传递语义意味着对函数中参数值(形参)的任何更改(即在函数中为形参分配新值)对调用者来说 不可见 。
以下是对前面列出的max
和print_table
函数的一些示例函数调用:
int val1, val2, result;
val1 = 6;
val2 = 10;
/* to call max, pass in two int values, and because max returns an
int value, assign its return value to a local variable (result)
*/
result = max(val1, val2); /* call max with argument values 6 and 10 */
printf("%d\n", result); /* prints out 10 */
result = max(11, 3); /* call max with argument values 11 and 3 */
printf("%d\n", result); /* prints out 11 */
result = max(val1 * 2, val2); /* call max with argument values 12 and 10 */
printf("%d\n", result); /* prints out 12 */
/* print_table does not return a value, but takes two arguments */
print_table(1, 20); /* prints a table of values from 1 to 20 */
print_table(val1, val2); /* prints a table of values from 6 to 10 */
这是完整程序的另一个示例,它显示了对max
函数的稍微不同的实现的调用,该函数有一个附加语句来更改其参数的值(“x = y”):
/* max: computes the larger of two int values
* x: one value
* y: the other value
* returns: the larger of x and y
*/
int max(int x, int y) {
int bigger;
bigger = x;
if (y > x) {
bigger = y;
// note: changing the parameter x's value here will not
// change the value of its corresponding argument
x = y;
}
printf(" in max, before return x: %d y: %d\n", x, y);
return bigger;
}
/* main: shows a call to max */
int main(void) {
int a, b, res;
printf("Enter two integer values: ");
scanf("%d%d", &a, &b);
res = max(a, b);
printf("The larger value of %d and %d is %d\n", a, b, res);
return 0;
}
以下输出显示了该程序的两次运行可能是什么样子。请注意两次运行中参数x
的值(从max
函数内部打印)的差异。具体来说,请注意,在第二次运行中更改参数x
的值不会影响调用返回后作为参数传递给max
的变量:
$ ./a.out
Enter two integer values: 11 7
in max, before return x: 11 y: 7
The larger value of 11 and 7 is 11
$ ./a.out
Enter two integer values: 13 100
in max, before return x: 100 y: 100
The larger value of 13 and 100 is 100
由于参数是通过值传递给函数的,因此更改其参数值之一的先前版本的max
函数的行为与未更改参数值的原始版本的max
行为相同。
1.4.1. The Stack
执行栈跟踪程序中活动函数的状态。每个函数调用都会创建一个新的栈帧( stack frame,有时称为活动帧或活动记录),其中包含其参数和局部变量值。栈顶的帧是活动帧;它代表当前正在执行的函数激活,并且只有其局部变量和参数在范围内。当调用函数时,会为其创建一个新的栈帧(在栈顶部 压栈),并在新帧中为其局部变量和参数分配空间。当函数返回时,其栈帧将从堆栈中删除(从堆栈顶部 弹出),将调用者的栈帧保留在栈顶部。
对于前面的示例程序,在max
执行return
语句之前的执行点,执行堆栈将类似于[图 1](https://diveintosystems.org/book/C1-C_intro/functions .html#FigFunctionSimple)。回想一下,由main
传递给max
的参数值是 按值传递 的,这意味着max
的形参,x
和y
被分配了它们相应来自main
中的调用的实参参数a
和b
的值。尽管max
函数更改了x
的值,但该更改不会影响main
中a
的值。
图 1. max 函数返回之前的执行堆栈内容
以下完整程序包含两个函数,并显示了从main
函数调用它们的示例。在此程序中,我们在main
函数上方声明max
和print_table
的函数原型,以便main
尽管首先定义,但仍可以访问它们。 main
函数包含整个程序的高级步骤,首先定义它与程序的自上而下的设计相呼应。此示例包含描述程序中对函数和函数调用重要的部分的注释。您还可以下载并运行完整程序。
/* This file shows examples of defining and calling C functions.
* It also demonstrates using scanf().
*/
#include <stdio.h>
/* This is an example of a FUNCTION PROTOTYPE. It declares just the type
* information for a function (the function's name, return type, and parameter
* list). A prototype is used when code in main wants to call the function
* before its full definition appears in the file.
*/
int max(int n1, int n2);
/* A prototype for another function. void is the return type of a function
* that does not return a value
*/
void print_table(int start, int stop);
/* All C programs must have a main function. This function defines what the
* program does when it begins executing, and it's typically used to organize
* the big-picture behavior of the program.
*/
int main(void) {
int x, y, larger;
printf("This program will operate over two int values.\n");
printf("Enter the first value: ");
scanf("%d", &x);
printf("Enter the second value: ");
scanf("%d", &y);
larger = max(x, y);
printf("The larger of %d and %d is %d\n", x, y, larger);
print_table(x, larger);
return 0;
}
/* This is an example of a FUNCTION DEFINITION. It specifies not only the
* function name and type, but it also fully defines the code of its body.
* (Notice, and emulate, the complete function comment!)
*/
/* Computes the max of two integer values.
* n1: the first value
* n2: the other value
* returns: the larger of n1 and n2
*/
int max(int n1, int n2) {
int result;
result = n1;
if (n2 > n1) {
result = n2;
}
return result;
}
/* prints out the squares from start to stop
* start: the beginning of the range
* stop: the end of the range
*/
void print_table(int start, int stop) {
int i;
for (i = start; i <= stop; i++) {
printf("%d\t", i*i);
}
printf("\n");
}